/**
 * Copyright (C) 2010 David Simonek <dafesimonek@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

/*
 * SongsTable.java
 *
 * Created on Jun 1, 2010, 12:28:26 PM
 */

package org.dafe.partyband.gui.songs;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.util.*;
import javax.swing.*;
import javax.swing.RowFilter.Entry;
import javax.swing.Timer;
import javax.swing.event.*;
import javax.swing.table.TableModel;
import org.dafe.partyband.db.api.ISong;
import org.dafe.partyband.db.api.ISongResource;
import org.dafe.partyband.db.api.handlers.ISongHandler;
import org.dafe.partyband.db.api.queries.ISongQuery;
import org.dafe.partyband.gui.AbstractSongTableModel;
import org.dafe.partyband.db.api.DbManager;
import org.dafe.partyband.gui.Utilities;
import org.dafe.partyband.gui.Utilities.CustomColumnFactory;
import org.dafe.partyband.gui.concerts.ConcertUtils;
import org.dafe.partyband.gui.exports.ExportWizardAction;
import org.dafe.partyband.gui.songs.ViewConfigurationPanel.Settings;
import org.dafe.partyband.importer.api.IImportObserver;
import org.dafe.partyband.importer.api.IImporterServices;
import org.dafe.partyband.importer.api.ISongImporter;
import org.jdesktop.swingx.JXTable;
import org.jdesktop.swingx.JXTable.GenericEditor;
import org.jdesktop.swingx.color.ColorUtil;
import org.jdesktop.swingx.decorator.ColorHighlighter;
import org.jdesktop.swingx.decorator.HighlightPredicate;
import org.jdesktop.swingx.decorator.HighlighterFactory;
import org.openide.DialogDisplayer;
import org.openide.NotifyDescriptor;
import org.openide.util.Lookup;
import org.openide.util.NbBundle;
import org.openide.util.lookup.AbstractLookup;
import org.openide.util.lookup.InstanceContent;

/**
 *
 * @author dafe
 */
public class SongsTable extends javax.swing.JPanel {
    private Action delSongsAction;
    private Action openTextAction;
    private Action playMp3Action;

    private final ISongHandler songHandler = DbManager.getBandManager().getHandlersLookup().lookup(ISongHandler.class);
    private final ISongQuery songQuery = DbManager.getBandManager().getQueriesLookup().lookup(ISongQuery.class);
    private final ISongImporter songImporter = Lookup.getDefault().lookup(IImporterServices.class).getSongImporter();

    private final Timer searchTimer;
    private final SongsModel songsModel;
    private final InstanceContent curSongLkpContent;
    private final AbstractLookup curSongLkp;
    private final InstanceContent selSongsResourcesContent;
    private final AbstractLookup selSongsResourcesLkp;

    private static final String[] columnNames = new String[] { "songName", "songAuthor" };
    private final InstanceContent selSongsContent;
    private final AbstractLookup selSongsLkp;
    private RowFilter<TableModel, Integer> searchFilter;
    private final ViewConfigurationPanel viewConfigPanel;
    private Settings viewConfigSettings;

    private static final String ALL_SONGS_FILTER = NbBundle.getMessage(SongsTable.class, "AllSongsFilter");
    private static final String NO_TEXT_FILTER = NbBundle.getMessage(SongsTable.class, "NoTextFilter");
    private static final String NO_AUDIO_FILTER = NbBundle.getMessage(SongsTable.class, "NoAudioFilter");
    private static final String NO_CONCERT_FILTER = NbBundle.getMessage(SongsTable.class, "NoConcertFilter");
    private static final String ONLY_CONCERT_FILTER = NbBundle.getMessage(SongsTable.class, "OnlyConcertFilter");
    private static final String FULL_SONGS_FILTER = NbBundle.getMessage(SongsTable.class, "FullSongsFilter");

    /** Creates new form SongsTable */
    public SongsTable() {
        initComponents();

        songsTable.addHighlighter(HighlighterFactory.createSimpleStriping());
        songsTable.addHighlighter(new ColorHighlighter(HighlightPredicate.ROLLOVER_ROW,
                ColorUtil.setAlpha(songsTable.getSelectionBackground(), 30), null));
        
        // define column features and properties
        CustomColumnFactory customCF = new Utilities.CustomColumnFactory();
        CustomColumnFactory.ColumnDescription nameDesc = new CustomColumnFactory.ColumnDescription();
        nameDesc.title = NbBundle.getMessage(SongsTable.class, "TIT_SongNameColumn");
        nameDesc.tooltip = NbBundle.getMessage(SongsTable.class, "HINT_SongNameColumn");
        GenericEditor genEditor = new JXTable.GenericEditor();
        genEditor.setClickCountToStart(3);
        nameDesc.cellEditor = genEditor;
        customCF.setColumnDescription(columnNames[0], nameDesc);

        CustomColumnFactory.ColumnDescription authorDesc = new CustomColumnFactory.ColumnDescription();
        authorDesc.title = NbBundle.getMessage(SongsTable.class, "TIT_SongAuthorColumn");
        authorDesc.tooltip = NbBundle.getMessage(SongsTable.class, "HINT_SongAuthorColumn");
        genEditor = new JXTable.GenericEditor();
        genEditor.setClickCountToStart(3);
        authorDesc.cellEditor = genEditor;
        customCF.setColumnDescription(columnNames[1], authorDesc);

        songsTable.setColumnFactory(customCF);

        songsModel = new SongsModel();
        songImporter.addImportObserver(songsModel);
        songsTable.setModel(songsModel);
        
        curSongLkpContent = new InstanceContent();
        curSongLkp = new AbstractLookup(curSongLkpContent);
        selSongsResourcesContent = new InstanceContent();
        selSongsResourcesLkp = new AbstractLookup(selSongsResourcesContent);
        selSongsContent = new InstanceContent();
        selSongsLkp = new AbstractLookup(selSongsContent);

        playMp3Action = ActionsFactory.createPlayMp3SongAction(selSongsLkp);
        openTextAction = ActionsFactory.createOpenTextSongAction(selSongsLkp);
        delSongsAction = ActionsFactory.createDeleteSongsAction(selSongsLkp, songsModel);
        ActionMap actionMap = songsTable.getActionMap();
        InputMap inputMap = songsTable.getInputMap();
        actionMap.put("OpenSongAction", openTextAction);
        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "OpenSongAction");
        actionMap.put("DelSongAction", delSongsAction);
        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), "DelSongAction");
        actionMap.put("PlayMp3Action", playMp3Action);
        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0), "PlayMp3Action");

        songsTable.getSelectionModel().addListSelectionListener(
                new ListSelectionListener() {

            public void valueChanged(ListSelectionEvent evt) {
                ListSelectionModel selModel = songsTable.getSelectionModel();
                SongsModel model = (SongsModel)songsTable.getModel();
                int index = selModel.getMinSelectionIndex();
                if (index != -1) {
                    index = songsTable.convertRowIndexToModel(index);
                    ISong song = model.getSongAtRow(index);
                    curSongLkpContent.set(Collections.EMPTY_LIST, null);
                    curSongLkpContent.add(song);
                }
                
                selSongsResourcesContent.set(Collections.EMPTY_LIST, null);
                for (ISongResource songRes : Utilities.getSelectedResources(songsTable, songsModel)) {
                    selSongsResourcesContent.add(songRes);
                }
                selSongsContent.set(Collections.EMPTY_LIST, null);
                for (ISong song : Utilities.getSelectedSongs(songsTable, songsModel)) {
                    selSongsContent.add(song);
                }
            }

        });

        createSearchFilter();

        searchTimer = new Timer(250, new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                applyFilters();
            }
        });
        searchTimer.setRepeats(false);
        
        searchField.getDocument().addDocumentListener(new DocumentListener() {

            public void insertUpdate(DocumentEvent e) {
                searchTimer.restart();
            }

            public void removeUpdate(DocumentEvent e) {
                searchTimer.restart();
            }

            public void changedUpdate(DocumentEvent e) {
                searchTimer.restart();
            }
        });

        viewConfigSettings = new ViewConfigurationPanel.Settings();
        viewConfigSettings.filterName = ALL_SONGS_FILTER;
        
        viewConfigPanel = new ViewConfigurationPanel(viewConfigSettings, createViewFilters(), new ChangeListener() {
            public void stateChanged(ChangeEvent e) {
                Settings oldSettings = viewConfigSettings;
                viewConfigSettings = viewConfigPanel.getSettings();
                if (oldSettings.filter != viewConfigSettings.filter) {
                    applyFilters();
                }
                // TBD - reaction to change of other settings
            }
        });

        System.out.println("Locale: " + Locale.getDefault());
    }

    private void createSearchFilter() {
        searchFilter = new RowFilter<TableModel, Integer>() {
            @Override
            public boolean include(Entry<? extends TableModel, ? extends Integer> entry) {
                SongsModel model = (SongsModel) entry.getModel();
                ISong song = model.getSongAtRow(entry.getIdentifier());
                return Utilities.isSongMatch(song, searchField.getText());
            }
        };
    }

    private Map<String, RowFilter<TableModel, Integer>> createViewFilters () {
        final RowFilter<TableModel, Integer> noText = new RowFilter<TableModel, Integer>() {
            @Override
            public boolean include(Entry<? extends TableModel, ? extends Integer> entry) {
                SongsModel model = (SongsModel) entry.getModel();
                ISong song = model.getSongAtRow(entry.getIdentifier());
                return Utilities.getTextWithChords(song) == null;
            }
        };
        final RowFilter<TableModel, Integer> noAudio = new RowFilter<TableModel, Integer>() {
            @Override
            public boolean include(Entry<? extends TableModel, ? extends Integer> entry) {
                SongsModel model = (SongsModel) entry.getModel();
                ISong song = model.getSongAtRow(entry.getIdentifier());
                return Utilities.getAudio(song) == null;
            }
        };
        final RowFilter<TableModel, Integer> noConcert = new RowFilter<TableModel, Integer>() {
            @Override
            public boolean include(Entry<? extends TableModel, ? extends Integer> entry) {
                SongsModel model = (SongsModel) entry.getModel();
                ISong song = model.getSongAtRow(entry.getIdentifier());
                return ConcertUtils.getPlayedOnConcertsCount(song) == 0;
            }
        };
        final RowFilter<TableModel, Integer> onlyConcert = new RowFilter<TableModel, Integer>() {
            @Override
            public boolean include(Entry<? extends TableModel, ? extends Integer> entry) {
                SongsModel model = (SongsModel) entry.getModel();
                ISong song = model.getSongAtRow(entry.getIdentifier());
                return ConcertUtils.getPlayedOnConcertsCount(song) > 0;
            }
        };
        final RowFilter<TableModel, Integer> fullSongs = new RowFilter<TableModel, Integer>() {
            @Override
            public boolean include(Entry<? extends TableModel, ? extends Integer> entry) {
                SongsModel model = (SongsModel) entry.getModel();
                ISong song = model.getSongAtRow(entry.getIdentifier());
                return Utilities.getTextWithChords(song) != null && Utilities.getAudio(song) != null;
            }
        };

        return new HashMap<String, RowFilter<TableModel, Integer>>() {{
            put(ALL_SONGS_FILTER, null);
            put(NO_TEXT_FILTER, noText);
            put(NO_AUDIO_FILTER, noAudio);
            put(NO_CONCERT_FILTER, noConcert);
            put(ONLY_CONCERT_FILTER, onlyConcert);
            put(FULL_SONGS_FILTER, fullSongs);
        }};
    }

    private void applyFilters () {
        if (viewConfigSettings.filter != null) {
            List<RowFilter<TableModel, Integer>> filters = new ArrayList<RowFilter<TableModel, Integer>>(2);
            filters.add(viewConfigSettings.filter);
            filters.add(searchFilter);
            songsTable.setRowFilter(RowFilter.andFilter(filters));
        } else {
            songsTable.setRowFilter(searchFilter);
        }
    }

    public Lookup getCurSongLkp() {
        return curSongLkp;
    }

    public final void refreshData () {
        songsModel.importPerformed();
    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        jXLabel1 = new org.jdesktop.swingx.JXLabel();
        searchField = new javax.swing.JTextField();
        jScrollPane1 = new javax.swing.JScrollPane();
        songsTable = new org.jdesktop.swingx.JXTable();

        jXLabel1.setText(org.openide.util.NbBundle.getMessage(SongsTable.class, "SongsTable.jXLabel1.text")); // NOI18N

        searchField.setText(org.openide.util.NbBundle.getMessage(SongsTable.class, "SongsTable.searchField.text")); // NOI18N
        searchField.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                searchFieldActionPerformed(evt);
            }
        });

        songsTable.setModel(new javax.swing.table.DefaultTableModel(
            new Object [][] {

            },
            new String [] {

            }
        ));
        songsTable.setShowHorizontalLines(false);
        songsTable.setShowVerticalLines(false);
        songsTable.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mouseClicked(java.awt.event.MouseEvent evt) {
                songsTableMouseClicked(evt);
            }
            public void mousePressed(java.awt.event.MouseEvent evt) {
                songsTableMousePressed(evt);
            }
            public void mouseReleased(java.awt.event.MouseEvent evt) {
                songsTableMouseReleased(evt);
            }
        });
        jScrollPane1.setViewportView(songsTable);

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 389, Short.MAX_VALUE)
                    .addGroup(layout.createSequentialGroup()
                        .addComponent(jXLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(searchField, javax.swing.GroupLayout.DEFAULT_SIZE, 333, Short.MAX_VALUE)))
                .addContainerGap())
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(jXLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(searchField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 263, Short.MAX_VALUE))
        );
    }// </editor-fold>//GEN-END:initComponents

    private void searchFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_searchFieldActionPerformed
        // TODO add your handling code here:
    }//GEN-LAST:event_searchFieldActionPerformed

    private void songsTableMousePressed(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_songsTableMousePressed
        showPopupMenu(evt);
    }//GEN-LAST:event_songsTableMousePressed

    private void songsTableMouseReleased(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_songsTableMouseReleased
        showPopupMenu(evt);
    }//GEN-LAST:event_songsTableMouseReleased

    private void songsTableMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_songsTableMouseClicked
        if (evt.getClickCount() == 2) {
            if (openTextAction.isEnabled()) {
                openTextAction.actionPerformed(null);
            }
        }
    }//GEN-LAST:event_songsTableMouseClicked

    private void showPopupMenu (MouseEvent evt) {
        if (!evt.isPopupTrigger()) {
            return;
        }
        Utilities.assureSelected(songsTable, evt.getPoint());
        
        JPopupMenu popup = new JPopupMenu();

        Utilities.addAction2Popup(popup, openTextAction);
        Utilities.addAction2Popup(popup, playMp3Action);
        if (popup.getSubElements().length > 0) {
            popup.add(new JSeparator());
        }
        Utilities.addAction2Popup(popup, ActionsFactory.createMergeSongsAction(selSongsLkp, songsModel));
        Utilities.addAction2Popup(popup, new ExportWizardAction(
                NbBundle.getMessage(SongsTable.class, "ACT_ExportSongs"), selSongsResourcesLkp));
        if (popup.getSubElements().length > 0) {
            popup.add(new JSeparator());
        }
        Utilities.addAction2Popup(popup, delSongsAction);
        Utilities.addAction2Popup(popup, ActionsFactory.createEditTableCellAction(evt.getPoint(), songsTable));
        
        popup.add(new JSeparator());
        Utilities.addMusicServices2Popup(popup, selSongsLkp);

        popup.show(songsTable, evt.getX(), evt.getY());
    }


    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JScrollPane jScrollPane1;
    private org.jdesktop.swingx.JXLabel jXLabel1;
    private javax.swing.JTextField searchField;
    private org.jdesktop.swingx.JXTable songsTable;
    // End of variables declaration//GEN-END:variables

    private void deleteSelectedSongs () {
        Object option = DialogDisplayer.getDefault().notify(new NotifyDescriptor.Confirmation(
                NbBundle.getMessage(SongsTable.class, "MSG_DeleteSong"),
                NbBundle.getMessage(SongsTable.class, "MSG_DeleteSongTitle")
        ));
        if (!NotifyDescriptor.OK_OPTION.equals(option)) {
            return;
        }
        
        int[] selRows = songsTable.getSelectedRows();
        for (int i = 0; i < selRows.length; i++) {
            songsModel.deleteSong(songsModel.getSongAtRow(
                    songsTable.convertRowIndexToModel(selRows[i])), true);
        }
    }

    public JComponent getSongsActionsPanel() {
        return new SongsActionsPanel(selSongsLkp, selSongsResourcesLkp, songsModel, viewConfigPanel);
    }


    public class SongsModel extends AbstractSongTableModel implements IImportObserver {

        private List<ISong> songs = Collections.EMPTY_LIST;

        public int getRowCount() {
            return songs.size();
        }

        public int getColumnCount() {
            return 2;
        }

        public Object getValueAt(int rowIndex, int columnIndex) {
            ISong song = songs.get(rowIndex);
            if (columnIndex == 0) {
                return song.getName();
            } else if (columnIndex == 1) {
                return song.getAuthor().getName();
            }
            throw new IllegalArgumentException("Column index out of range: " + columnIndex);
        }

        @Override
        public boolean isCellEditable(int rowIndex, int columnIndex) {
            return true;
        }

        @Override
        public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
            ISong song = songs.get(rowIndex);
            switch (columnIndex) {
                case 0:
                    song.setName((String) aValue);
                    songHandler.storeSongName(song);
                    break;
                case 1:
                    song.getAuthor().setName((String) aValue);
                    songHandler.storeAuthorName(song);
                    break;
                default:
                    throw new IllegalArgumentException();
            }
            fireTableCellUpdated(rowIndex, columnIndex);
        }

        @Override
        public String getColumnName(int column) {
            return columnNames[column];
        }

        @Override
        public Class<?> getColumnClass(int columnIndex) {
            return String.class;
        }

        public ISong getSongAtRow (int index) {
            return songs.get(index);
        }

        public void deleteSong (ISong song, boolean deleteResources) {
            songs.remove(song);
            songHandler.removeSong(song, deleteResources);
            fireTableDataChanged();
        }

        public void importPerformed() {
            songs = new ArrayList<ISong>(songQuery.getAllSongs());
            fireTableDataChanged();
        }

    }

}
